<?php
class DbMssql extends Db
{
    private static $_instance = null;

    public static function getInstance()
    {
        if (is_null(self::$_instance)) self::$_instance = new self();
        ini_set('mssql.datetimeconvert', 0);
        return self::$_instance;
    }

    private $_connectId;

    public function createConnect($connectId)
    {
        $this->_connectId = $connectId;
    }

    private static $_connect = array();

    private function _connect()
    {
        $connectId = $this->_connectId;
        if (!$connectId) $connectId = md5(serialize($this->config));
        if (isset(self::$_connect[$connectId])) return self::$_connect[$connectId];
        $function = $this->config['db_long_connect'] == true ? 'mssql_pconnect' : 'mssql_connect';
        self::$_connect[$connectId] = @$function($this->config['db_host'], $this->config['db_user'], $this->config['db_password']);
        if (!self::$_connect[$connectId]) {
            throw new Exception(__METHOD__.' [数据库连接失败: '.$this->config['db_host'].']');
        }
        return self::$_connect[$connectId];
    }

    public function escapeString($string)
    {
        return str_replace('\'', "''", $string);
    }

    protected $sql = 'SELECT T1.* FROM (SELECT yytphp.*, ROW_NUMBER() OVER(%ORDER%) AS ROW_NUMBER FROM (SELECT %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%) AS yytphp) AS T1 %LIMIT%';

    public function parseSql($sql, $args = array())
    {
        $sql = str_replace(array('%TABLE%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%ORDER%', '%LIMIT%'), array(
            $this->getTable($args),
            self::parseField($args['field']),
            $this->parse('join', $args['join']),
            $this->parse('where', $args['where']),
            $this->parse('group', $args['group']),
            self::parseOrder($args['order']),
            self::parseLimit($args['limit'])),
            $sql);
        return $sql;
    }

    public static function parseLimit($limit)
    {
        if (empty($limit)) return '';
        $limit = explode(',', $limit);
        if (count($limit) > 1) {
            $limitStr =	'(T1.ROW_NUMBER BETWEEN '.$limit[0].' + 1 AND '.$limit[0].' + '.$limit[1].')';
        } else {
            $limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND '.$limit[0].")";
        }
        return 'WHERE '.$limitStr;
    }

    public static function parseOrder($order)
    {
        return !empty($order) ? 'ORDER BY '.$order : 'ORDER BY rand()';
    }

    public static function parseField($params)
    {
        if (is_array($params)) {
            if (!$params) return '*';
            $fields = array();
            foreach ($params as $field) {
                $field = trim($field);
                if ($field != '*') $field = '['.$field.']';
                $fields[] = $field;
            }
            return join(',', $fields);
        } else {
            return trim($params) ? $params : '*';
        }
    }

    public function fetch()
    {
        $sql = $this->parseSql($this->sql, $this->getArgs());
        return $this->query($sql, __FUNCTION__);
    }

    public function fetchAll()
    {
        $sql = $this->parseSql($this->sql, $this->getArgs());
        return $this->query($sql, __FUNCTION__);
    }

    public function insert($data)
    {
        $args = $this->getArgs();
        $table = $this->getTable($args);
        $data = $this->filterData($data, $table);
        $fields = $values = array();
        foreach ($data as $field => $value) {
            $fields[] = $field;
            $values[] = '\''.$this->escapeString($value).'\'';
        }
        $field = $fields ? join(',', $fields) : '';
        $value = $values ? join(',', $values) : '';
        if (!$field) return 0; //如果字段全部为空就直接返回
        $sql = 'INSERT INTO '.$table.' ('.$field.') VALUES ('.$value.')';
        return $this->query($sql, __FUNCTION__);
    }

    public function update($data)
    {
        $args = $this->getArgs();
        $table = $this->getTable($args);
        $where = $this->parse('where', $args['where']);
        if (!$where) return 0; //如果不存在修改条件直接返回
        $value = '';
        if (is_array($data)) {
            $data = $this->filterData($data, $table);
            $values = array();
            foreach ($data as $field => $value) {
                $values[] = $field.' = \''.$this->escapeString($value).'\'';
            }
            if ($values) $value = join(',', $values);
        } else {
            $value = $data;
        }
        $sql = 'UPDATE '.$table.' SET '.$value.$where;
        return $this->query($sql, __FUNCTION__);
    }

    private function _query($sql)
    {
        $connect = $this->_connect();
        if (!@mssql_select_db($this->config['db_name'], $connect)) {
            throw new Exception(__METHOD__.' [数据库不存在: '.$this->config['db_name'].']');
        }
        $query = @mssql_query(self::setDataCharset($sql, 'GBK'), $connect);
        if (!$query) throw new Exception(self::setDataCharset(mssql_get_last_message()));
        return $query;
    }

    public function query($sql, $method = '')
    {
        $startTime = microtime(true);
        $method = strtolower($method);
        $sql = $this->parse('prefix', $sql);

        if (!$method) return $this->_query($sql);

        $result = array();
        switch ($method) {
            case 'fetch':
                $query = $this->_query($sql);
                $result = self::setDataCharset(mssql_fetch_assoc($query));
                break;
            case 'fetchall':
                $query = $this->_query($sql);
                while ($row = mssql_fetch_assoc($query)) {
                    $result[] = self::setDataCharset($row);
                }
                break;
            case 'count':
                $query = $this->_query($sql);
                $result = mssql_fetch_assoc($query);
                $result = !empty($result) ? reset($result) : 0;
                break;
            case 'insert':
                $this->_query($sql);
                $result = $this->lastInsertId();
                break;
            case 'update':
            case 'delete':
                $this->_query($sql);
                $result = mssql_rows_affected($this->_connect());
                break;
        }
        self::$countQuery++;
        $stopTime = microtime(true);
        Web::debug('[用时<font color="red">'.round(($stopTime - $startTime), 4).'</font>秒]: '.$sql);
        return $result === null ? array() : $result;
    }

    public static function setDataCharset($data, $charset = 'UTF-8')
    {
        if (is_array($data)) {
            $result = array();
            foreach ($data as $key => $value) {
                $key = self::setCharset($key, $charset);
                if (is_array($value)) {
                    $value = self::setDataCharset($data, $charset);
                } else {
                    $value = self::setCharset($value, $charset);
                }
                $result[$key] = $value;
            }
            return $result;
        } else {
            return self::setCharset($data, $charset);
        }
    }

    public static function setCharset($string, $charset = 'UTF-8')
    {
        if ($charset == 'UTF-8') {
            if (self::isUtf8($string)) return $string;
            return iconv('GBK', 'UTF-8', $string);
        } else if ($charset == 'GBK') {
            if (!self::isUtf8($string)) return $string;
            return iconv('UTF-8', 'GBK', $string);
        }
        return $string;
    }

    public static function isUtf8($word)
    {
        if (preg_match("/^([".chr(228)."-".chr(233)."]{1}[".chr(128)."-".chr(191)."]{1}[".chr(128)."-".chr(191)."]{1}){1}/", $word) == true || preg_match("/([".chr(228)."-".chr(233)."]{1}[".chr(128)."-".chr(191)."]{1}[".chr(128)."-".chr(191)."]{1}){1}$/", $word) == true || preg_match("/([".chr(228)."-".chr(233)."]{1}[".chr(128)."-".chr(191)."]{1}[".chr(128)."-".chr(191)."]{1}){2,}/", $word) == true) {
            return true;
        }
        return false;
    }

    public function getFields()
    {
        if (!$this->table) return;
        $dbname = '';
        if (!empty($this->config['db_name']) && !stristr($this->table, $this->config['db_name'])) {
            $dbname = $this->config['db_name'].'.';
        }
        $cacheFile = Web::config('cache_path').'/db/'.$dbname.$this->table.'.php';
        if (is_file($cacheFile)) {
            return unserialize(str_replace('<?php exit();//', '', file_get_contents($cacheFile)));
        }

        $startTime = microtime(true);

        $sql = 'SELECT Name FROM SysColumns WHERE id=Object_Id(\''.$this->table.'\')';
        try {
            $query = $this->_query($sql);
            $fields = array();
            while ($row = mssql_fetch_assoc($query)) {
                $fields[] = strtolower(self::setDataCharset($row['Name']));
            }
            $fields['pk'] = array_shift($fields);
            self::$countQuery++;
            if (!empty($fields['pk'])) {
                $stopTime = microtime(true);
                Web::debug('[用时<font color="red">'.round(($stopTime - $startTime), 4).'</font>秒]: '.$sql, 2);
                Web::makeDir(dirname($cacheFile));
                file_put_contents($cacheFile, '<?php exit();//'.serialize($fields));
                return $fields;
            }
        } catch (Exception $e) {
            throw new Exception(__METHOD__.' [获取表'.$this->table.'字段失败: '.$e->getMessage().']');
        }
    }

    public function beginTransaction()
    {
        $this->_query('BEGIN TRAN');
    }

    public function commit()
    {
        $this->_query('COMMIT TRAN');
    }

    public function rollBack()
    {
        $this->_query('ROLLBACK TRAN');
    }

    public function lastInsertId()
    {
        $sql = 'SELECT SCOPE_IDENTITY() as last_insert_id';
        $query = $this->_query($sql);
        list($lastInsertId) = mssql_fetch_row($query);
        mssql_free_result($query);
        return $lastInsertId;
    }

    public function getVersion()
    {
        $query = $this->_query('SELECT SERVERPROPERTY(\'productversion\')');
        $arr = mssql_fetch_assoc($query);
        return $arr['computed'];
    }

    public function close()
    {
        return mssql_close(self::$_connect[$connectId]);
    }
}